const ccxt = require('ccxt')
// const TI = require('trading-indicator')
// const _ = require('lodash')
// const Decimal = require('decimal.js')
// const moment = require('moment')
const {
    Api,
    KlineService,
    BollService,
    LogService
} = require('./../service/index')
const {
    BollStatus,
    OrderDirection,
    StoreName,
    OrderDirectionDesc
  } = require('./../constants')
const { default: Decimal } = require('_decimal.js@10.2.1@decimal.js')

const print = LogService.print

class binanceV2 {
    constructor () {
        this.state = {
            exchangeId: 'binance',
            apiKey: '0HXJp9xoYq3OeVKwmu5f6tbzp7kLVoRxNzoDgiddKHReUpt1TN1RmlPLB1xkZsYG',
            secret: 'DETPaJmbJE5kqCXhJ3a2slHMTMjGgyf9DtbLrhDmKUKYL8YZ2Uw3bIE6nB52eabd',
            tick: 'BTC/USDT',
            symbol: 'BTCUSDT',
            // minBollWidthRatio: 0.00328,
            minBollWidthRatio: 0.0015,
            // minBollWidthRatio: 0.002,
            // minBollWidth: 190,
            interval: '3m',
            loopDuration: 3000,
            transactionType: 'future',
            leverage: 100,
            buildRatio: 0.3,
            isMock: false,
            isSetTakeProfitToBalance: true,
            info: []
        }
    }
    main = () => {
        // this._clear()
        Promise.all([
            // 获取当前最新价格
            Api.requestPublicPrice(),
            // 查询账户余额
            Api.requestBalance(),
            // 查询开单列表
            Api.requestOpenOrders(),
            // 查询布林带数据
            Api.requestBoll(),
            // 查询最近50条K线蜡烛
            Api.requestKlinesV2(),
            // 查询是否越过RSI
            Api.requestRSI(),
            // 查询是否越过StochRSI
            Api.requestStochRSI()
        ]).then(([
            price,
            {
                balance,                     // 总余额
                holdingAmount: _holdingAmount,                  // 持仓金额
                openOrderAmount,                // 挂单金额
                availableBalance: freeAmount,   // 可用余额（未下单的钱）
                isFree: _isFree                         // 是否空仓
            },
            openOrders,
            {
                bollUp,
                bollMiddle,
                bollDown,
                bollWidth,
                lastBollUp,
                lastBollMiddle,
                lastBollDown,
                bollList
            },
            klineList,
            {
                isRSIOverUp,
                isRSIOverDown,
                RSI
            },
            {
                isStochRSIOverUp,
                isStochRSIOverDown,
                stochRSI
            }
        ]) => {
            const { minBollWidthRatio, isMock } = this.state
            const minBollWidth = Decimal(price).mul(minBollWidthRatio).toNumber()
            // 1. 根据布林线判断当前形态
            const bollStatus = BollService.getCurrentStatus({
                bollList,
                klineList,
                minBollWidth,
                price
            })

            const holdingAmount = isMock ? 100 : _holdingAmount
            const isFree = isMock ? false : _isFree
            // 重要数据塞入state
            this.state.info = {
                price,
                holdingAmount,
                openOrderAmount,
                freeAmount,
                isFree,
                openOrders,
                bollUp,
                bollMiddle,
                bollWidth,
                bollDown,
                lastBollUp,
                lastBollMiddle,
                lastBollDown,
                bollList,
                klineList,
                minBollWidth,
                bollStatus,
                isRSIOverUp,
                isRSIOverDown,
                isStochRSIOverUp,
                isStochRSIOverDown,
                balance,
                RSI,
                stochRSI
            }
            Api.addState(this.state.info)

            LogService.initFormatLog(this.state.info)

            if (isFree) {
                if (!openOrders.length) {
                    // 空仓中，且无挂单
                    this._handleFree()
                } else {
                    // 已结束，还有挂单
                    this._handleClosing()
                }
            } else {
                if (!holdingAmount) {
                    // 挂单中
                    this._handleOpening()
                } else {
                    // 持有中
                    this._handleHolding()
                }
            }
            this._interval = setTimeout(this.main, this.state.loopDuration)
        })
    }
    // 空仓中
    _handleFree () {
        const {
            freeAmount,
            bollStatus: currentBollStatus,
            bollList,
            klineList,
            bollWidth,
            minBollWidth,
            price
        } = this.state.info

        switch (currentBollStatus) {
            /* 震荡时，突破布林线带立即开单 */
            case BollStatus.SHAKE:
                const direction = BollService.getShakeOpenOrderDirection(price, bollList)
                const xFactor = BollService.getShakeXFactor(bollList, direction)
                LogService.addParams({
                    xFactor
                })
                switch (direction) {
                    case OrderDirection.BUY:
                        print('震荡到下轨，开多单', 'order')
                        Api.requestOpenOrderNow(OrderDirection.BUY, freeAmount, currentBollStatus)
                        break;

                    case OrderDirection.SELL:
                        print('震荡到上轨，开空单', 'order')
                        Api.requestOpenOrderNow(OrderDirection.SELL, freeAmount, currentBollStatus)
                        break;
                
                    default:
                        print('震荡中', 'normal')
                        break;
                }
                break;
            case BollStatus.ONESIDEUP:
                LogService.addParams({
                    xFactor: null
                })
                if (
                    KlineService.getShouldOneOpenOrder(price, klineList, OrderDirection.BUY, bollList, minBollWidth, bollWidth)
                ) {
                    print('单边上涨，开单！', 'order')
                    Api.requestOpenOrderNow(OrderDirection.BUY, freeAmount, currentBollStatus)
                } else {
                    print('单边上涨，但未达到开单条件', 'normal')
                }
                break;

            case BollStatus.ONESIDEDOWN:
                LogService.addParams({
                    xFactor: null
                })
                if (
                    KlineService.getShouldOneOpenOrder(price, klineList, OrderDirection.SELL, bollList, minBollWidth, bollWidth)
                ) {
                    print('单边下跌，开单！', 'order')
                    Api.requestOpenOrderNow(OrderDirection.SELL, freeAmount, currentBollStatus)
                } else {
                    print('单边下跌，但未达到开单条件', 'normal')
                }
                break;

            case BollStatus.NARROW:
                LogService.addParams({
                    xFactor: null
                })
                // 收窄当震荡来做
                // const [narrowDirection, reason] = BollService.getNarrowOpenDirection(bollList, price)
                // if (narrowDirection) {
                //     print(`收窄，但布林半径稳定，符合开单条件，挂${OrderDirectionDesc[narrowDirection]}！`, 'order')
                //     Api.requestOpenOrderNow(narrowDirection, freeAmount, currentBollStatus)
                // } else {
                //     print(`收窄，但${reason}，窄通道内不开单`, 'normal')
                // }
                print(`收窄到${bollWidth.toFixed()}，没有利润空间，不做`, 'normal')
                break;
            default:
                print('空仓中，且无挂单', 'normal')
                break;
        }
    }
    _handleOpening () {
        const {
            bollUp,
            bollWidth,
            bollDown,
            price
        } = this.state.info

        if (
            price <
                Decimal(bollUp)
                .sub(bollWidth * 0.5)
                .toNumber() &&
            price >
                Decimal(bollDown)
                .add(bollWidth * 0.5)
                .toNumber()
        ) {
            // 回到布林轨道内，撤单
            print('回到布林轨道正常范围，撤单', 'normal')
            Api.requestCancelAll()
        } else {
            print('挂单中，未成交', 'normal')
        }
    }
    _handleHolding () {
        const {
            klineList,
            bollList,
            bollMiddle,
            minBollWidth,
            bollWidth,
            isRSIOverUp,
            isRSIOverDown,
            isStochRSIOverUp,
            isStochRSIOverDown,
            price,
            holdingAmount,
            bollStatus
        } = this.state.info

        const {
            leverage
        } = this.state

        const orderBollStatus = LogService.getState(StoreName.HOLDING_ORDER_BOLL_STATUS)
        // 用holdingAmount兜底
        const orderBollBtcQuantity = Number(LogService.getState(StoreName.HOLDING_ORDER_BTC_QUANTITY)) || Decimal(holdingAmount).mul(leverage).div(price).toNumber()

        const {
            highestPrice,
            lowestPrice
        } = klineList[klineList.length - 1]

        const direction = LogService.getState(StoreName.HOLDING_ORDER_DIRECTION)

        switch (orderBollStatus) {
            case BollStatus.SHAKE:
                // 如果目前状态是单边，平掉震荡单
                if ([BollStatus.ONESIDEUP, BollStatus.ONESIDEDOWN].includes(bollStatus)) {
                    print('布林带宽度扩大，震荡转为单边，震荡单平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else {
                    switch (direction) {
                        case OrderDirection.BUY:
                            if (price > bollMiddle) {
                                print('震荡多单回归，平仓！', 'order')
                                Api.requestEndOrderNow(orderBollBtcQuantity)
                            } else {
                                print('震荡多单持有中', 'normal')
                            }
                            break;
                        case OrderDirection.SELL:
                            if (price < bollMiddle) {
                                print('震荡空单回归，平仓！', 'order')
                                Api.requestEndOrderNow(orderBollBtcQuantity)
                            } else {
                                print('震荡空单持有中', 'normal')
                            }
                            break;
                        default:
                            break;
                    }
                }
                break;
            
            // 出现连续两根反向蜡烛，则立即平仓
            case BollStatus.ONESIDEUP:
                if (KlineService.getShouldOneSideStop (klineList, direction, bollList, minBollWidth)) {
                    print('单边上涨出现连续两根反向蜡烛，或者交易量巨大，平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else if (KlineService.getShouldOneSideEnd(OrderDirection.BUY, bollList, price)) {
                    print('单边上涨回到布林中轨，平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else {
                    print('持有单边上涨', 'normal')
                }
                // else if (isRSIOverUp && isStochRSIOverUp) {
                //     print('单边上涨，突破RSI和StochRSI极限超买点，平仓！', 'order')
                //     Api.requestEndOrderNow(orderBollBtcQuantity)
                // } 
                
                break;

            case BollStatus.ONESIDEDOWN:
                if (KlineService.getShouldOneSideStop (klineList, direction, bollList, minBollWidth)) {
                    print('单边下跌出现连续两根反向蜡烛，或者交易量巨大，平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else if (KlineService.getShouldOneSideEnd(OrderDirection.SELL, bollList, price)) {
                    print('单边下跌回到布林中轨，平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else {
                    print('持有单边下跌', 'normal')
                }
                break;
            
            case BollStatus.NARROW:
                if (KlineService.isKlineReverse(direction, klineList)) {
                    print('窄通道，遇到两根反向蜡烛，平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else if (bollWidth > (minBollWidth - 50)) {
                    print('窄通道变宽，有可能迎来单边，平仓！', 'order')
                    Api.requestEndOrderNow(orderBollBtcQuantity)
                } else {
                    switch (direction) {
                        case OrderDirection.BUY:
                            if (price > bollMiddle) {
                                print('窄通道多单回归，平仓！', 'order')
                                Api.requestEndOrderNow(orderBollBtcQuantity)
                            } else {
                                print('窄通道多单持有中', 'normal')
                            }
                            break;
                        case OrderDirection.SELL:
                            if (price < bollMiddle) {
                                print('窄通道空单回归，平仓！', 'order')
                                Api.requestEndOrderNow(orderBollBtcQuantity)
                            } else {
                                print('窄通道空单持有中', 'normal')
                            }
                            break;
                        default:
                            break;
                    }
                }
                
            default:
                break;
        }
    }
    _handleClosing () {
        // 撤销所有挂单
        Api.requestCancelAll()
    }
    _login () {
        const {  
            exchangeId,
            interval,
            symbol,
            tick,
            buildRatio,
            apiKey,
            secret,
            leverage,
            isMock,
            isSetTakeProfitToBalance
        } = this.state
            const exchangeClass = ccxt[exchangeId],
            exchange = new exchangeClass({
            apiKey,
            secret,
            password: '031612',
            timeout: 30000,
            enableRateLimit: true,
            options: {
                defaultType: this.state.transactionType,
                type: this.state.transactionType
            }
            })
        this.$exchange = exchange
        Api.setState({
            buildRatio,
            exchange,
            tick,
            interval,
            symbol,
            exchangeId,
            leverage,
            isMock,
            isSetTakeProfitToBalance
        })
        // 设倍率
        Api.requestSetLeverage(leverage)
    }
    _clear () {}
    start () {
        const { exchangeId, tick } = this.state
        // 登录
        this._login()
        console.log(`———— 开始监听${exchangeId}：${tick}行情 ————`)
        // 轮询
        this._interval = setTimeout(this.main, this.state.loopDuration)
    }
}
module.exports = new binanceV2()